/**
 * Moondance UML modeling tool
 * 
 * @author Dascalu Laurentiu (dascalu.laurentziu@gmail.com).
 * 
 * This program is free software; you can redistribute it and
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */

package ro.pub.cs.se.moondance.xmi.deconverter;

import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Queue;
import java.util.StringTokenizer;

import org.eclipse.draw2d.geometry.Dimension;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import ro.pub.cs.se.moondance.MoondanceData;
import ro.pub.cs.se.moondance.model.AggregationConnection;
import ro.pub.cs.se.moondance.model.AssociationConnection;
import ro.pub.cs.se.moondance.model.ClassFunction;
import ro.pub.cs.se.moondance.model.ClassVariable;
import ro.pub.cs.se.moondance.model.CompositionConnection;
import ro.pub.cs.se.moondance.model.Connection;
import ro.pub.cs.se.moondance.model.InheritanceConnection;
import ro.pub.cs.se.moondance.model.ModelElement;
import ro.pub.cs.se.moondance.model.Parameter;
import ro.pub.cs.se.moondance.model.RealizationConnection;
import ro.pub.cs.se.moondance.model.UMLBaseClass;
import ro.pub.cs.se.moondance.model.UMLClass;
import ro.pub.cs.se.moondance.model.UMLClassDiagram;
import ro.pub.cs.se.moondance.model.UMLDiagram;
import ro.pub.cs.se.moondance.model.UMLEntity;
import ro.pub.cs.se.moondance.model.UMLInterface;
import ro.pub.cs.se.moondance.model.UMLPackage;
import ro.pub.cs.se.moondance.xmi.converter.XMICommon;

public class XMIUMLDiagramDeconverter implements IXMIDeconverter {
	public final static Dimension DEFAULT_BASE_CLASS_DIMENSION = new Dimension(
			100, 150);
	public final static Dimension DEFAULT_PACKET_DIMENSION = new Dimension(400,
			300);
	public final static String ATTRIBUTE = "Attribute";
	public final static String OPERATION = "Operation";
	public final static String PARAMETER = "param";
	private static int attributeCounter = 0;
	private static int operationCounter = 0;
	private static int parametersCounter = 0;
	private static Hashtable<String, UMLEntity> idToEntity = new Hashtable<String, UMLEntity>();

	/**
	 * Internal class for serializing inheritance connections
	 */
	private class QueueCommand {
		private String targetID;
		private UMLEntity source;

		public QueueCommand(UMLEntity source, String targetID) {
			this.setTargetID(targetID);
			this.setSource(source);
		}

		public void setTargetID(String targetID) {
			this.targetID = targetID;
		}

		public String getTargetID() {
			return targetID;
		}

		public void setSource(UMLEntity source) {
			this.source = source;
		}

		public UMLEntity getSource() {
			return source;
		}

	}

	private Queue<QueueCommand> inheritanceCommands;

	private ClassVariable getClassVariable(Element root) {
		ClassVariable var = new ClassVariable();

		boolean isStatic = false;
		String initialValue = null;
		String visibility = null;
		int multiplicity = -1;

		if (root.getAttribute(XMICommon.IS_STATIC) != null
				&& root.getAttribute(XMICommon.IS_STATIC).equals("true"))
			isStatic = true;

		var.setStatic(isStatic);

		visibility = root.getAttribute(XMICommon.VISIBILITY);

		NodeList nodeList = root.getChildNodes();

		for (int i = 0; i < nodeList.getLength(); i++) {
			if (nodeList.item(i) instanceof Element) {
				Element currentNode = (Element) nodeList.item(i);

				if (currentNode.getTagName().equals(XMICommon.DEFAULT_VALUE)) {
					if (currentNode.getAttribute(XMICommon.VALUE) != null)
						initialValue = currentNode
								.getAttribute(XMICommon.VALUE);
				}
			}
		}

		if (initialValue != null)
			var.setInitialValue(initialValue);

		if (root.getAttribute(XMICommon.NAME) == null
				|| root.getAttribute(XMICommon.NAME).length() < 1)
			var.setName(ATTRIBUTE + attributeCounter++);
		else
			var.setName(root.getAttribute(XMICommon.NAME));

		if (root.getAttribute(XMICommon.TYPE) == null
				|| root.getAttribute(XMICommon.TYPE).length() < 1)
			var.setType("type");
		else {
			var.setMultiplicity(multiplicity);
			var.setType(root.getAttribute(XMICommon.TYPE));
		}

		if (visibility == null)
			var.setVisibility("public");
		else
			var.setVisibility(visibility);

		return var;
	}

	private ClassFunction getClassFunction(Element root, UMLBaseClass parent,
			String className) {

		ClassFunction func = new ClassFunction();
		boolean isAbstract = false;
		boolean isConstructor = false;
		boolean isDestructor = false;
		boolean isStatic = false;
		String visibility = null;
		String returnType = null;
		ArrayList<Parameter> params = new ArrayList<Parameter>();
		String name = root.getAttribute(XMICommon.NAME);

		if (root.getAttribute(XMICommon.IS_ABSTRACT) != null
				&& root.getAttribute(XMICommon.IS_ABSTRACT).equals("true"))
			isAbstract = true;
		if (root.getAttribute(XMICommon.IS_STATIC) != null
				&& root.getAttribute(XMICommon.IS_STATIC).equals("true"))
			isStatic = true;

		visibility = root.getAttribute(XMICommon.VISIBILITY);

		NodeList nodeList = root.getChildNodes();

		for (int i = 0; i < nodeList.getLength(); i++) {
			if (nodeList.item(i) instanceof Element) {
				Element currentNode = (Element) nodeList.item(i);

				if (currentNode.getTagName().equals(XMICommon.OWNED_PARAMETER)) {
					if (currentNode.getAttribute(XMICommon.DIRECTION).equals(
							XMICommon.RETURN))
						returnType = currentNode.getAttribute(XMICommon.TYPE);
					else if (currentNode.getAttribute(XMICommon.DIRECTION)
							.equals(XMICommon.IN)) {
						StringTokenizer strtok = new StringTokenizer(
								currentNode.getAttribute(XMICommon.TYPE), " []");
						Parameter e = null;
						boolean hasMultiplicity = false;
						String type = null;

						try {
							type = strtok.nextToken();
							strtok.nextToken();

							hasMultiplicity = true;
							e = new Parameter(PARAMETER + parametersCounter++,
									currentNode.getAttribute(XMICommon.TYPE),
									false);
						} catch (Exception ex) {
							type = currentNode.getAttribute(XMICommon.TYPE);

						}

						e = new Parameter(PARAMETER + parametersCounter++,
								type, hasMultiplicity);
						params.add(e);
					}
				}
			}
		}

		if (name != null && name.length() > 0) {
			if (name.equals(className))
				isConstructor = true;
			else if (name.startsWith("~"))
				isDestructor = true;
			func.setName(name);
		} else
			func.setName(OPERATION + operationCounter++);

		func.setAbstract(isAbstract);
		func.setConstructor(isConstructor);
		func.setDestructor(isDestructor);

		func.setParameters(params);
		func.setParent(parent);
		func.setReturnType(returnType);
		func.setStatic(isStatic);

		if (visibility == null)
			func.setVisibility("public");
		else
			func.setVisibility(visibility);

		return func;
	}

	private UMLClass getClass(Element root, ModelElement parent) {

		UMLClass myClass = new UMLClass();
		myClass.setName(root.getAttribute(XMICommon.NAME));

		idToEntity.put(root.getAttribute(XMICommon.XMI_ID), myClass);

		if (root.getAttribute(XMICommon.VISIBILITY) != null)
			myClass.setVisibility(root.getAttribute(XMICommon.VISIBILITY));

		if (root.getAttribute(XMICommon.IS_ABSTRACT) != null
				&& root.getAttribute(XMICommon.IS_ABSTRACT).equals("true"))
			myClass.setAbstractClass(true);

		myClass.setParent(parent);
		myClass.setSize(DEFAULT_BASE_CLASS_DIMENSION);

		NodeList nodeList = root.getChildNodes();

		for (int i = 0; i < nodeList.getLength(); i++) {
			if (nodeList.item(i) instanceof Element) {
				Element currentNode = (Element) nodeList.item(i);

				if (currentNode.getTagName().equals(XMICommon.OWNED_ATTRIBUTE))
					myClass.addAttribute(getClassVariable(currentNode));
				else if (currentNode.getTagName().equals(
						XMICommon.OWNED_OPERATION))
					myClass.addOperation(getClassFunction(currentNode, myClass,
							myClass.getName()));
				else if (currentNode.getTagName().equals(
						XMICommon.GENERALIZATION)) {
					inheritanceCommands.add(new QueueCommand(myClass,
							currentNode.getAttribute(XMICommon.GENERAL)));
				}
			}
		}

		return myClass;
	}

	private UMLInterface getInterface(Element root, ModelElement parent) {
		UMLInterface myInterface = new UMLInterface();

		idToEntity.put(root.getAttribute(XMICommon.XMI_ID), myInterface);

		myInterface.setName(root.getAttribute(XMICommon.NAME));

		if (root.getAttribute(XMICommon.VISIBILITY) != null)
			myInterface.setVisibility(root.getAttribute(XMICommon.VISIBILITY));

		myInterface.setInterfaceClass(true);
		myInterface.setParent(parent);
		myInterface.setSize(DEFAULT_BASE_CLASS_DIMENSION);

		NodeList nodeList = root.getChildNodes();

		for (int i = 0; i < nodeList.getLength(); i++) {
			if (nodeList.item(i) instanceof Element) {
				Element currentNode = (Element) nodeList.item(i);

				if (currentNode.getTagName().equals(XMICommon.OWNED_ATTRIBUTE))
					myInterface.addAttribute(getClassVariable(currentNode));
				else if (currentNode.getTagName().equals(
						XMICommon.OWNED_OPERATION))
					myInterface.addOperation(getClassFunction(currentNode,
							myInterface, myInterface.getName()));
			}
		}

		return myInterface;
	}

	private void createAssociation(Element root) {
		/**
		 * Can be: ASSOCIATION, DIRECT ASSOCIATION, AGGREGATION, COMPOSITE
		 */
		Element firstChild = null;
		Element secondChild = null;

		int type = -1;

		NodeList nodeList = root.getChildNodes();

		for (int i = 0; i < nodeList.getLength(); i++)
			if (nodeList.item(i) instanceof Element) {
				if (firstChild == null)
					firstChild = (Element) nodeList.item(i);
				else
					secondChild = (Element) nodeList.item(i);
			}

		UMLEntity source = idToEntity.get(firstChild
				.getAttribute(XMICommon.TYPE));
		UMLEntity target = idToEntity.get(secondChild
				.getAttribute(XMICommon.TYPE));

		if (secondChild.getAttribute(XMICommon.AGGREGATION) == null)
			type = 0;
		else if (secondChild.getAttribute(XMICommon.AGGREGATION).equals(
				XMICommon.SHARED))
			type = 1;
		else if (secondChild.getAttribute(XMICommon.AGGREGATION).equals(
				XMICommon.COMPOSITE))
			type = 2;

		Connection conn = null;

		if (type == 0) {
			conn = new AssociationConnection(source, target);
			conn.setSourceMultiplicity("1");
			conn.setTargetMultiplicity("1");
			conn.setName(root.getAttribute(XMICommon.NAME));
		} else if (type == 1) {
			conn = new AggregationConnection(source, target);
			conn.setSourceMultiplicity("1..*");
			conn.setTargetMultiplicity("1");
			conn.setName(root.getAttribute(XMICommon.NAME));
		} else if (type == 2) {
			conn = new CompositionConnection(target, source);
			conn.setSourceMultiplicity("1..*");
			conn.setTargetMultiplicity("1");
			conn.setName(root.getAttribute(XMICommon.NAME));
		}

		conn.reconnect(source, target);
		target.addConnection(conn);
		source.addConnection(conn);
	}

	private UMLPackage getPackage(Element root, ModelElement parent) {
		UMLPackage myPackage = new UMLPackage();

		myPackage.setName(root.getAttribute(XMICommon.NAME));
		myPackage.setParent(parent);

		NodeList nodeList = root.getChildNodes();

		for (int i = 0; i < nodeList.getLength(); i++) {
			if (nodeList.item(i) instanceof Element) {
				Element currentNode = (Element) nodeList.item(i);

				if (currentNode.getTagName().equals(XMICommon.PACKED_ELEMENT)) {
					String currentNodeType = currentNode
							.getAttribute(XMICommon.XMI_TYPE);
					if (currentNodeType.equals(XMICommon.UML_CLASS))
						myPackage.addClass(getClass(currentNode, myPackage));
					else if (currentNodeType.equals(XMICommon.UML_INTERFACE))
						myPackage
								.addClass(getInterface(currentNode, myPackage));
					else if (currentNodeType.equals(XMICommon.UML_PACKAGE))
						myPackage
								.addPackage(getPackage(currentNode, myPackage));
					else if (currentNodeType.equals(XMICommon.UML_ASSOCIATION))
						createAssociation(currentNode);
					else if (currentNodeType.equals(XMICommon.UML_REALIZATION)) {
						String supplier = currentNode
								.getAttribute(XMICommon.SUPLIER);
						UMLEntity source = idToEntity.get(supplier);
						String client = currentNode
								.getAttribute(XMICommon.CLIENT);
						UMLEntity target = idToEntity.get(client);
						Connection conn = new RealizationConnection(source,
								target);
						conn.setSourceMultiplicity("1");
						conn.setTargetMultiplicity("1");
						conn.setName(currentNode.getAttribute(XMICommon.NAME));

						target.addConnection(conn);
						source.addConnection(conn);
						conn.reconnect(source, target);
					} else
						MoondanceData.exitWithError();
				} else
					MoondanceData.exitWithError();
			}
		}

		myPackage.setSize(DEFAULT_PACKET_DIMENSION);

		return myPackage;
	}

	private void startAdd(Element root, UMLDiagram parent) {
		NodeList nodeList = root.getChildNodes();

		for (int i = 0; i < nodeList.getLength(); i++) {
			if (nodeList.item(i) instanceof Element) {
				Element currentNode = (Element) nodeList.item(i);

				if (currentNode.getTagName().equals(XMICommon.PACKED_ELEMENT)) {
					String currentNodeType = currentNode
							.getAttribute(XMICommon.XMI_TYPE);
					if (currentNodeType.equals(XMICommon.UML_CLASS))
						parent.addChild(getClass(currentNode, parent));
					else if (currentNodeType.equals(XMICommon.UML_INTERFACE))
						parent.addChild(getInterface(currentNode, parent));
					else if (currentNodeType.equals(XMICommon.UML_PACKAGE))
						parent.addChild(getPackage(currentNode, parent));
					else if (currentNodeType.equals(XMICommon.UML_ASSOCIATION))
						createAssociation(currentNode);
					else if (currentNodeType.equals(XMICommon.UML_REALIZATION)) {
						String supplier = currentNode
								.getAttribute(XMICommon.SUPLIER);
						UMLEntity source = idToEntity.get(supplier);
						String client = currentNode
								.getAttribute(XMICommon.CLIENT);
						UMLEntity target = idToEntity.get(client);
						Connection conn = new RealizationConnection(source,
								target);
						conn.setSourceMultiplicity("1");
						conn.setTargetMultiplicity("1");
						conn.setName(currentNode.getAttribute(XMICommon.NAME));

						target.addConnection(conn);
						source.addConnection(conn);
						conn.reconnect(source, target);
					} else
						MoondanceData.exitWithError();
				} else
					MoondanceData.exitWithError();
			}
		}
	}

	private UMLDiagram newDiagram(Element root) {
		return new UMLClassDiagram("Imported XMI class diagram");
	}

	private void addInheritance() {
		for (Iterator<QueueCommand> it = inheritanceCommands.iterator(); it
				.hasNext();) {
			QueueCommand comm = it.next();
			UMLEntity target = idToEntity.get(comm.getTargetID());
			UMLEntity source = comm.getSource();
			Connection conn = new InheritanceConnection(source, target);
			conn.setName("is a");
			target.addConnection(conn);
			source.addConnection(conn);
			conn.reconnect(source, target);
		}
	}

	@Override
	public UMLDiagram deconvert(Element root) {
		inheritanceCommands = new LinkedList<QueueCommand>();
		UMLDiagram diagram = newDiagram(root);
		startAdd(root, diagram);
		addInheritance();
		return diagram;
	}
}
